function getServerURL() {
    var serverURL = '';
    if (typeof app !== 'undefined' && app && typeof app == 'object' && (app?.getSetting || false) && typeof app.getSetting === 'function') {
        serverURL = app.getSetting('server_url') || '';
        if (serverURL.endsWith('/')) serverURL = serverURL.slice(0, -1);
    }
    return serverURL;
}
function render_threads(server_url = '', exit_callback = null) {
    $('.thread').each(function() {
        const thread = $(this);
        const skip_link = thread.hasClass('skip-link');
        const target_blank = (typeof server_url === 'string' && server_url.length > 0) ? ' target="_blank"' : '';

        // Text fields
        const content = thread.attr('data-content') || '';
        const url = thread.attr('data-url') || '';
        const channel = thread.attr('data-channel') || '';
        const channelId = thread.attr('data-channel-id') || 0;
        const replyCount = thread.attr('data-reply-count') || 0;

        // IDs 
        const chatId = thread.attr('data-chat-id') || '';
        const authorPublicKey = thread.attr('data-author-public-key') || '';
        const authorDisplayName = thread.attr('data-author-display-name') || '';
        const authorAlias = thread.attr('data-author-alias') || '';
        const authorPic = thread.attr('data-author-pic') || '';
        const datetimeCreated = thread.attr('data-datetime-created') || '';

        // Build HTML
        var replyStr = '';
        if (replyCount && !isNaN(replyCount * 1) && replyCount * 1) {
            replyStr = `<span><span class="text-muted small thread_reply_ticker">${replyCount}</span>&nbsp;${bsi('chat-fill')}</span>`;
        }
        var chat_link = `<a href="${server_url}/thread/${chatId}"${target_blank} class="link_to_thread" data-chat-id="${chatId}" data-author-public-key="${authorPublicKey}">${chatId}</a>`;
        if (skip_link) {
            chat_link = `<strong>${chatId}</strong>`;
        }
        var content_link = `<a href="${server_url}/thread/${chatId}"${target_blank} class="link_to_thread content_link" data-chat-id="${chatId}" data-author-public-key="${authorPublicKey}">${content}</a>`;
        if (skip_link) {
            content_link = content;
        }
        var exit_link = '';
        if (exit_callback && typeof exit_callback == 'function') {
            exit_link = `<a class="go-back-link" href="/" style="padding-right:10px;"><span class="bi bi-x-lg"></span></a>`;
        }
        var picImg = '';
        if (authorPic && typeof authorPic == 'string' && authorPic.length > 0 && authorPic.startsWith('http')) {
            picImg = `<img src="${authorPic}" class="rounded-circle" style="width:20px;height:20px;margin-right:5px;">`;
        }
        var aliasClass = '';
        if (authorAlias && typeof authorAlias == 'string' && authorAlias.length > 0) {
            aliasClass = 'has_alias';
        } else {
            aliasClass = 'no_alias';
        }
        thread.empty().append(`
             <small class="text-muted">
                 ${exit_link}${chat_link} by
                 <a href="${server_url}/profile/${authorPublicKey}"${target_blank} class="${aliasClass} user-profile-link" data-public-key="${authorPublicKey}">${picImg}${authorDisplayName.trim()}</a>
                 <span class="float-end text-muted">
                    ${replyStr}
                     <small class="text-muted relative-time" data-time="${datetimeCreated}">${datetimeCreated}</small>
                     <a class="chat-options-opener text-muted small" data-type="thread" data-chat-id="${chatId}" data-author-public-key="${authorPublicKey}">
                        ${bsi('three-dots-vertical')}
                     </a>
                 </span>
             </small>
             <br>
             <span class="thread-content-span">
                 ${channel ? `<span class="badge rounded-pill bg-secondary"><a href="${server_url}/channel_redirect/${channelId}"${target_blank}>${channel}</a></span>` : ''} 
                 ${content_link}
             </span>
             <div class="reaction-container" data-chat-id="${chatId}">
                 <button class="btn btn-sm btn-outline-secondary text-muted" style="opacity:0.5;">
                     ⮝ ...
                  </button>
                 <button class="btn btn-sm btn-outline-secondary text-muted" style="opacity:0.5;">
                     ⮟ ...
                  </button>
             </div>
        `);

        if (exit_callback && typeof exit_callback == 'function') {
            exit_link = thread.find('.go-back-link');
            exit_link.on('click', exit_callback);
            render_bsi();
        }

        if (url) {
            const link_toggle = $(`<a class="thread-link-toggle" data-chat-id="${chatId}" title="Toggle URL visibility">🔗</a>`);
            link_toggle.on('click', function() {
                const linkContainer = $(this).siblings('.thread-link-container');
                linkContainer.toggle(200);
            });
            thread.find('.thread-content-span').append(
                link_toggle,
                `
                <div class="thread-link-container" style="display:none;" data-chat-id="${chatId}">
                <a href="${url}" target="_blank" rel="noopener noreferrer">${url}</a>
                </div>`
            );
        }
        try {
            setTimeout(formatRelativeTime, 10);
        } catch (e) {
            console.error(e);
        }
    });

    // Update reactions (requires reactions.js)
    if (typeof loadBatchedReactions === 'function') {
        loadBatchedReactions();
    }

    // Add follow links (requires follow.js)
    init_follow_links(getServerURL());
}